1   /*
2    * Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved.
3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License version 2 only, as
7    * published by the Free Software Foundation.  Oracle designates this
8    * particular file as subject to the "Classpath" exception as provided
9    * by Oracle in the LICENSE file that accompanied this code.
10   *
11   * This code is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14   * version 2 for more details (a copy is included in the LICENSE file that
15   * accompanied this code).
16   *
17   * You should have received a copy of the GNU General Public License version
18   * 2 along with this work; if not, write to the Free Software Foundation,
19   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20   *
21   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22   * or visit www.oracle.com if you need additional information or have any
23   * questions.
24   */
25  package javax.swing;
26  
27  import sun.swing.SwingUtilities2;
28  
29  import java.awt.*;
30  import java.awt.event.*;
31  import java.lang.reflect.*;
32  import java.net.*;
33  import java.util.*;
34  import java.io.*;
35  import java.util.*;
36  
37  import javax.swing.plaf.*;
38  import javax.swing.text.*;
39  import javax.swing.event.*;
40  import javax.swing.text.html.*;
41  import javax.accessibility.*;
42  
43  /**
44   * A text component to edit various kinds of content.
45   * You can find how-to information and examples of using editor panes in
46   * <a href="http://java.sun.com/docs/books/tutorial/uiswing/components/text.html">Using Text Components</a>,
47   * a section in <em>The Java Tutorial.</em>
48   *
49   * <p>
50   * This component uses implementations of the
51   * <code>EditorKit</code> to accomplish its behavior. It effectively
52   * morphs into the proper kind of text editor for the kind
53   * of content it is given.  The content type that editor is bound
54   * to at any given time is determined by the <code>EditorKit</code> currently
55   * installed.  If the content is set to a new URL, its type is used
56   * to determine the <code>EditorKit</code> that should be used to
57   * load the content.
58   * <p>
59   * By default, the following types of content are known:
60   * <dl>
61   * <dt><b>text/plain</b>
62   * <dd>Plain text, which is the default the type given isn't
63   * recognized.  The kit used in this case is an extension of
64   * <code>DefaultEditorKit</code> that produces a wrapped plain text view.
65   * <dt><b>text/html</b>
66   * <dd>HTML text.  The kit used in this case is the class
67   * <code>javax.swing.text.html.HTMLEditorKit</code>
68   * which provides HTML 3.2 support.
69   * <dt><b>text/rtf</b>
70   * <dd>RTF text.  The kit used in this case is the class
71   * <code>javax.swing.text.rtf.RTFEditorKit</code>
72   * which provides a limited support of the Rich Text Format.
73   * </dl>
74   * <p>
75   * There are several ways to load content into this component.
76   * <ol>
77   * <li>
78   * The {@link #setText setText} method can be used to initialize
79   * the component from a string.  In this case the current
80   * <code>EditorKit</code> will be used, and the content type will be
81   * expected to be of this type.
82   * <li>
83   * The {@link #read read} method can be used to initialize the
84   * component from a <code>Reader</code>.  Note that if the content type is HTML,
85   * relative references (e.g. for things like images) can't be resolved
86   * unless the &lt;base&gt; tag is used or the <em>Base</em> property
87   * on <code>HTMLDocument</code> is set.
88   * In this case the current <code>EditorKit</code> will be used,
89   * and the content type will be expected to be of this type.
90   * <li>
91   * The {@link #setPage setPage} method can be used to initialize
92   * the component from a URL.  In this case, the content type will be
93   * determined from the URL, and the registered <code>EditorKit</code>
94   * for that content type will be set.
95   * </ol>
96   * <p>
97   * Some kinds of content may provide hyperlink support by generating
98   * hyperlink events.  The HTML <code>EditorKit</code> will generate
99   * hyperlink events if the <code>JEditorPane</code> is <em>not editable</em>
100  * (<code>JEditorPane.setEditable(false);</code> has been called).
101  * If HTML frames are embedded in the document, the typical response would be
102  * to change a portion of the current document.  The following code
103  * fragment is a possible hyperlink listener implementation, that treats
104  * HTML frame events specially, and simply displays any other activated
105  * hyperlinks.
106  * <code><pre>
107 
108 &nbsp;    class Hyperactive implements HyperlinkListener {
109 &nbsp;
110 &nbsp;        public void hyperlinkUpdate(HyperlinkEvent e) {
111 &nbsp;            if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
112 &nbsp;                JEditorPane pane = (JEditorPane) e.getSource();
113 &nbsp;                if (e instanceof HTMLFrameHyperlinkEvent) {
114 &nbsp;                    HTMLFrameHyperlinkEvent  evt = (HTMLFrameHyperlinkEvent)e;
115 &nbsp;                    HTMLDocument doc = (HTMLDocument)pane.getDocument();
116 &nbsp;                    doc.processHTMLFrameHyperlinkEvent(evt);
117 &nbsp;                } else {
118 &nbsp;                    try {
119 &nbsp;                        pane.setPage(e.getURL());
120 &nbsp;                    } catch (Throwable t) {
121 &nbsp;                        t.printStackTrace();
122 &nbsp;                    }
123 &nbsp;                }
124 &nbsp;            }
125 &nbsp;        }
126 &nbsp;    }
127 
128  * </pre></code>
129  * <p>
130  * For information on customizing how <b>text/html</b> is rendered please see
131  * {@link #W3C_LENGTH_UNITS} and {@link #HONOR_DISPLAY_PROPERTIES}
132  * <p>
133  * Culturally dependent information in some documents is handled through
134  * a mechanism called character encoding.  Character encoding is an
135  * unambiguous mapping of the members of a character set (letters, ideographs,
136  * digits, symbols, or control functions) to specific numeric code values. It
137  * represents the way the file is stored. Example character encodings are
138  * ISO-8859-1, ISO-8859-5, Shift-jis, Euc-jp, and UTF-8. When the file is
139  * passed to an user agent (<code>JEditorPane</code>) it is converted to
140  * the document character set (ISO-10646 aka Unicode).
141  * <p>
142  * There are multiple ways to get a character set mapping to happen
143  * with <code>JEditorPane</code>.
144  * <ol>
145  * <li>
146  * One way is to specify the character set as a parameter of the MIME
147  * type.  This will be established by a call to the
148  * {@link #setContentType setContentType} method.  If the content
149  * is loaded by the {@link #setPage setPage} method the content
150  * type will have been set according to the specification of the URL.
151  * It the file is loaded directly, the content type would be expected to
152  * have been set prior to loading.
153  * <li>
154  * Another way the character set can be specified is in the document itself.
155  * This requires reading the document prior to determining the character set
156  * that is desired.  To handle this, it is expected that the
157  * <code>EditorKit</code>.read operation throw a
158  * <code>ChangedCharSetException</code> which will
159  * be caught.  The read is then restarted with a new Reader that uses
160  * the character set specified in the <code>ChangedCharSetException</code>
161  * (which is an <code>IOException</code>).
162  * </ol>
163  * <p>
164  * <dl>
165  * <dt><b><font size=+1>Newlines</font></b>
166  * <dd>
167  * For a discussion on how newlines are handled, see
168  * <a href="text/DefaultEditorKit.html">DefaultEditorKit</a>.
169  * </dl>
170  *
171  * <p>
172  * <strong>Warning:</strong> Swing is not thread safe. For more
173  * information see <a
174  * href="package-summary.html#threading">Swing's Threading
175  * Policy</a>.
176  * <p>
177  * <strong>Warning:</strong>
178  * Serialized objects of this class will not be compatible with
179  * future Swing releases. The current serialization support is
180  * appropriate for short term storage or RMI between applications running
181  * the same version of Swing.  As of 1.4, support for long term storage
182  * of all JavaBeans<sup><font size="-2">TM</font></sup>
183  * has been added to the <code>java.beans</code> package.
184  * Please see {@link java.beans.XMLEncoder}.
185  *
186  * @beaninfo
187  *   attribute: isContainer false
188  * description: A text component to edit various types of content.
189  *
190  * @author  Timothy Prinzing
191  */
192 public class JEditorPane extends JTextComponent {
193 
194     /**
195      * Creates a new <code>JEditorPane</code>.
196      * The document model is set to <code>null</code>.
197      */
198     public JEditorPane() {
199         super();
200         setFocusCycleRoot(true);
201         setFocusTraversalPolicy(new LayoutFocusTraversalPolicy() {
202                 public Component getComponentAfter(Container focusCycleRoot,
203                                                    Component aComponent) {
204                     if (focusCycleRoot != JEditorPane.this ||
205                         (!isEditable() && getComponentCount() > 0)) {
206                         return super.getComponentAfter(focusCycleRoot,
207                                                        aComponent);
208                     } else {
209                         Container rootAncestor = getFocusCycleRootAncestor();
210                         return (rootAncestor != null)
211                             ? rootAncestor.getFocusTraversalPolicy().
212                                   getComponentAfter(rootAncestor,
213                                                     JEditorPane.this)
214                             : null;
215                     }
216                 }
217                 public Component getComponentBefore(Container focusCycleRoot,
218                                                     Component aComponent) {
219                     if (focusCycleRoot != JEditorPane.this ||
220                         (!isEditable() && getComponentCount() > 0)) {
221                         return super.getComponentBefore(focusCycleRoot,
222                                                         aComponent);
223                     } else {
224                         Container rootAncestor = getFocusCycleRootAncestor();
225                         return (rootAncestor != null)
226                             ? rootAncestor.getFocusTraversalPolicy().
227                                   getComponentBefore(rootAncestor,
228                                                      JEditorPane.this)
229                             : null;
230                     }
231                 }
232                 public Component getDefaultComponent(Container focusCycleRoot)
233                 {
234                     return (focusCycleRoot != JEditorPane.this ||
235                             (!isEditable() && getComponentCount() > 0))
236                         ? super.getDefaultComponent(focusCycleRoot)
237                         : null;
238                 }
239                 protected boolean accept(Component aComponent) {
240                     return (aComponent != JEditorPane.this)
241                         ? super.accept(aComponent)
242                         : false;
243                 }
244             });
245         LookAndFeel.installProperty(this,
246                                     "focusTraversalKeysForward",
247                                     JComponent.
248                                     getManagingFocusForwardTraversalKeys());
249         LookAndFeel.installProperty(this,
250                                     "focusTraversalKeysBackward",
251                                     JComponent.
252                                     getManagingFocusBackwardTraversalKeys());
253     }
254 
255     /**
256      * Creates a <code>JEditorPane</code> based on a specified URL for input.
257      *
258      * @param initialPage the URL
259      * @exception IOException if the URL is <code>null</code>
260      *          or cannot be accessed
261      */
262     public JEditorPane(URL initialPage) throws IOException {
263         this();
264         setPage(initialPage);
265     }
266 
267     /**
268      * Creates a <code>JEditorPane</code> based on a string containing
269      * a URL specification.
270      *
271      * @param url the URL
272      * @exception IOException if the URL is <code>null</code> or
273      *          cannot be accessed
274      */
275     public JEditorPane(String url) throws IOException {
276         this();
277         setPage(url);
278     }
279 
280     /**
281      * Creates a <code>JEditorPane</code> that has been initialized
282      * to the given text.  This is a convenience constructor that calls the
283      * <code>setContentType</code> and <code>setText</code> methods.
284      *
285      * @param type mime type of the given text
286      * @param text the text to initialize with; may be <code>null</code>
287      * @exception NullPointerException if the <code>type</code> parameter
288      *          is <code>null</code>
289      */
290     public JEditorPane(String type, String text) {
291         this();
292         setContentType(type);
293         setText(text);
294     }
295 
296     /**
297      * Adds a hyperlink listener for notification of any changes, for example
298      * when a link is selected and entered.
299      *
300      * @param listener the listener
301      */
302     public synchronized void addHyperlinkListener(HyperlinkListener listener) {
303         listenerList.add(HyperlinkListener.class, listener);
304     }
305 
306     /**
307      * Removes a hyperlink listener.
308      *
309      * @param listener the listener
310      */
311     public synchronized void removeHyperlinkListener(HyperlinkListener listener) {
312         listenerList.remove(HyperlinkListener.class, listener);
313     }
314 
315     /**
316      * Returns an array of all the <code>HyperLinkListener</code>s added
317      * to this JEditorPane with addHyperlinkListener().
318      *
319      * @return all of the <code>HyperLinkListener</code>s added or an empty
320      *         array if no listeners have been added
321      * @since 1.4
322      */
323     public synchronized HyperlinkListener[] getHyperlinkListeners() {
324         return listenerList.getListeners(javax.swing.event.HyperlinkListener.class);
325     }
326 
327     /**
328      * Notifies all listeners that have registered interest for
329      * notification on this event type.  This is normally called
330      * by the currently installed <code>EditorKit</code> if a content type
331      * that supports hyperlinks is currently active and there
332      * was activity with a link.  The listener list is processed
333      * last to first.
334      *
335      * @param e the event
336      * @see EventListenerList
337      */
338     public void fireHyperlinkUpdate(HyperlinkEvent e) {
339         // Guaranteed to return a non-null array
340         Object[] listeners = listenerList.getListenerList();
341         // Process the listeners last to first, notifying
342         // those that are interested in this event
343         for (int i = listeners.length-2; i>=0; i-=2) {
344             if (listeners[i]==HyperlinkListener.class) {
345                 ((HyperlinkListener)listeners[i+1]).hyperlinkUpdate(e);
346             }
347         }
348     }
349 
350 
351     /**
352      * Sets the current URL being displayed.  The content type of the
353      * pane is set, and if the editor kit for the pane is
354      * non-<code>null</code>, then
355      * a new default document is created and the URL is read into it.
356      * If the URL contains and reference location, the location will
357      * be scrolled to by calling the <code>scrollToReference</code>
358      * method. If the desired URL is the one currently being displayed,
359      * the document will not be reloaded. To force a document
360      * reload it is necessary to clear the stream description property
361      * of the document. The following code shows how this can be done:
362      *
363      * <pre>
364      *   Document doc = jEditorPane.getDocument();
365      *   doc.putProperty(Document.StreamDescriptionProperty, null);
366      * </pre>
367      *
368      * If the desired URL is not the one currently being
369      * displayed, the <code>getStream</code> method is called to
370      * give subclasses control over the stream provided.
371      * <p>
372      * This may load either synchronously or asynchronously
373      * depending upon the document returned by the <code>EditorKit</code>.
374      * If the <code>Document</code> is of type
375      * <code>AbstractDocument</code> and has a value returned by
376      * <code>AbstractDocument.getAsynchronousLoadPriority</code>
377      * that is greater than or equal to zero, the page will be
378      * loaded on a separate thread using that priority.
379      * <p>
380      * If the document is loaded synchronously, it will be
381      * filled in with the stream prior to being installed into
382      * the editor with a call to <code>setDocument</code>, which
383      * is bound and will fire a property change event.  If an
384      * <code>IOException</code> is thrown the partially loaded
385      * document will
386      * be discarded and neither the document or page property
387      * change events will be fired.  If the document is
388      * successfully loaded and installed, a view will be
389      * built for it by the UI which will then be scrolled if
390      * necessary, and then the page property change event
391      * will be fired.
392      * <p>
393      * If the document is loaded asynchronously, the document
394      * will be installed into the editor immediately using a
395      * call to <code>setDocument</code> which will fire a
396      * document property change event, then a thread will be
397      * created which will begin doing the actual loading.
398      * In this case, the page property change event will not be
399      * fired by the call to this method directly, but rather will be
400      * fired when the thread doing the loading has finished.
401      * It will also be fired on the event-dispatch thread.
402      * Since the calling thread can not throw an <code>IOException</code>
403      * in the event of failure on the other thread, the page
404      * property change event will be fired when the other
405      * thread is done whether the load was successful or not.
406      *
407      * @param page the URL of the page
408      * @exception IOException for a <code>null</code> or invalid
409      *          page specification, or exception from the stream being read
410      * @see #getPage
411      * @beaninfo
412      *  description: the URL used to set content
413      *        bound: true
414      *       expert: true
415      */
416     public void setPage(URL page) throws IOException {
417         if (page == null) {
418             throw new IOException("invalid url");
419         }
420         URL loaded = getPage();
421 
422 
423         // reset scrollbar
424         if (!page.equals(loaded) && page.getRef() == null) {
425             scrollRectToVisible(new Rectangle(0,0,1,1));
426         }
427         boolean reloaded = false;
428         Object postData = getPostData();
429         if ((loaded == null) || !loaded.sameFile(page) || (postData != null)) {
430             // different url or POST method, load the new content
431 
432             int p = getAsynchronousLoadPriority(getDocument());
433             if (p < 0) {
434                 // open stream synchronously
435                 InputStream in = getStream(page);
436                 if (kit != null) {
437                     Document doc = initializeModel(kit, page);
438 
439                     // At this point, one could either load up the model with no
440                     // view notifications slowing it down (i.e. best synchronous
441                     // behavior) or set the model and start to feed it on a separate
442                     // thread (best asynchronous behavior).
443                     p = getAsynchronousLoadPriority(doc);
444                     if (p >= 0) {
445                         // load asynchronously
446                         setDocument(doc);
447                         synchronized(this) {
448                             pageLoader = new PageLoader(doc, in, loaded, page);
449                             pageLoader.execute();
450                         }
451                         return;
452                     }
453                     read(in, doc);
454                     setDocument(doc);
455                     reloaded = true;
456                 }
457             } else {
458                 // we may need to cancel background loading
459                 if (pageLoader != null) {
460                     pageLoader.cancel(true);
461                 }
462 
463                 // Do everything in a background thread.
464                 // Model initialization is deferred to that thread, too.
465                 pageLoader = new PageLoader(null, null, loaded, page);
466                 pageLoader.execute();
467                 return;
468             }
469         }
470         final String reference = page.getRef();
471         if (reference != null) {
472             if (!reloaded) {
473                 scrollToReference(reference);
474             }
475             else {
476                 // Have to scroll after painted.
477                 SwingUtilities.invokeLater(new Runnable() {
478                     public void run() {
479                         scrollToReference(reference);
480                     }
481                 });
482             }
483             getDocument().putProperty(Document.StreamDescriptionProperty, page);
484         }
485         firePropertyChange("page", loaded, page);
486     }
487 
488     /**
489      * Create model and initialize document properties from page properties.
490      */
491     private Document initializeModel(EditorKit kit, URL page) {
492         Document doc = kit.createDefaultDocument();
493         if (pageProperties != null) {
494             // transfer properties discovered in stream to the
495             // document property collection.
496             for (Enumeration<String> e = pageProperties.keys(); e.hasMoreElements() ;) {
497                 String key = e.nextElement();
498                 doc.putProperty(key, pageProperties.get(key));
499             }
500             pageProperties.clear();
501         }
502         if (doc.getProperty(Document.StreamDescriptionProperty) == null) {
503             doc.putProperty(Document.StreamDescriptionProperty, page);
504         }
505         return doc;
506     }
507 
508     /**
509      * Return load priority for the document or -1 if priority not supported.
510      */
511     private int getAsynchronousLoadPriority(Document doc) {
512         return (doc instanceof AbstractDocument ?
513             ((AbstractDocument) doc).getAsynchronousLoadPriority() : -1);
514     }
515 
516     /**
517      * This method initializes from a stream.  If the kit is
518      * set to be of type <code>HTMLEditorKit</code>, and the
519      * <code>desc</code> parameter is an <code>HTMLDocument</code>,
520      * then it invokes the <code>HTMLEditorKit</code> to initiate
521      * the read. Otherwise it calls the superclass
522      * method which loads the model as plain text.
523      *
524      * @param in the stream from which to read
525      * @param desc an object describing the stream
526      * @exception IOException as thrown by the stream being
527      *          used to initialize
528      * @see JTextComponent#read
529      * @see #setDocument
530      */
531     public void read(InputStream in, Object desc) throws IOException {
532 
533         if (desc instanceof HTMLDocument &&
534             kit instanceof HTMLEditorKit) {
535             HTMLDocument hdoc = (HTMLDocument) desc;
536             setDocument(hdoc);
537             read(in, hdoc);
538         } else {
539             String charset = (String) getClientProperty("charset");
540             Reader r = (charset != null) ? new InputStreamReader(in, charset) :
541                 new InputStreamReader(in);
542             super.read(r, desc);
543         }
544     }
545 
546 
547     /**
548      * This method invokes the <code>EditorKit</code> to initiate a
549      * read.  In the case where a <code>ChangedCharSetException</code>
550      * is thrown this exception will contain the new CharSet.
551      * Therefore the <code>read</code> operation
552      * is then restarted after building a new Reader with the new charset.
553      *
554      * @param in the inputstream to use
555      * @param doc the document to load
556      *
557      */
558     void read(InputStream in, Document doc) throws IOException {
559         if (! Boolean.TRUE.equals(doc.getProperty("IgnoreCharsetDirective"))) {
560             final int READ_LIMIT = 1024 * 10;
561             in = new BufferedInputStream(in, READ_LIMIT);
562             in.mark(READ_LIMIT);
563         }
564         try {
565             String charset = (String) getClientProperty("charset");
566             Reader r = (charset != null) ? new InputStreamReader(in, charset) :
567                 new InputStreamReader(in);
568             kit.read(r, doc, 0);
569         } catch (BadLocationException e) {
570             throw new IOException(e.getMessage());
571         } catch (ChangedCharSetException changedCharSetException) {
572             String charSetSpec = changedCharSetException.getCharSetSpec();
573             if (changedCharSetException.keyEqualsCharSet()) {
574                 putClientProperty("charset", charSetSpec);
575             } else {
576                 setCharsetFromContentTypeParameters(charSetSpec);
577             }
578             try {
579                 in.reset();
580             } catch (IOException exception) {
581                 //mark was invalidated
582                 in.close();
583                 URL url = (URL)doc.getProperty(Document.StreamDescriptionProperty);
584                 if (url != null) {
585                     URLConnection conn = url.openConnection();
586                     in = conn.getInputStream();
587                 } else {
588                     //there is nothing we can do to recover stream
589                     throw changedCharSetException;
590                 }
591             }
592             try {
593                 doc.remove(0, doc.getLength());
594             } catch (BadLocationException e) {}
595             doc.putProperty("IgnoreCharsetDirective", Boolean.valueOf(true));
596             read(in, doc);
597         }
598     }
599 
600 
601     /**
602      * Loads a stream into the text document model.
603      */
604     class PageLoader extends SwingWorker<URL, Object> {
605 
606         /**
607          * Construct an asynchronous page loader.
608          */
609         PageLoader(Document doc, InputStream in, URL old, URL page) {
610             this.in = in;
611             this.old = old;
612             this.page = page;
613             this.doc = doc;
614         }
615 
616         /**
617          * Try to load the document, then scroll the view
618          * to the reference (if specified).  When done, fire
619          * a page property change event.
620          */
621         protected URL doInBackground() {
622             boolean pageLoaded = false;
623             try {
624                 if (in == null) {
625                     in = getStream(page);
626                     if (kit == null) {
627                         // We received document of unknown content type.
628                         UIManager.getLookAndFeel().
629                                 provideErrorFeedback(JEditorPane.this);
630                         return old;
631                     }
632                 }
633 
634                 if (doc == null) {
635                     try {
636                         SwingUtilities.invokeAndWait(new Runnable() {
637                             public void run() {
638                                 doc = initializeModel(kit, page);
639                                 setDocument(doc);
640                             }
641                         });
642                     } catch (InvocationTargetException ex) {
643                         UIManager.getLookAndFeel().provideErrorFeedback(
644                                                             JEditorPane.this);
645                         return old;
646                     } catch (InterruptedException ex) {
647                         UIManager.getLookAndFeel().provideErrorFeedback(
648                                                             JEditorPane.this);
649                         return old;
650                     }
651                 }
652 
653                 read(in, doc);
654                 URL page = (URL) doc.getProperty(Document.StreamDescriptionProperty);
655                 String reference = page.getRef();
656                 if (reference != null) {
657                     // scroll the page if necessary, but do it on the
658                     // event thread... that is the only guarantee that
659                     // modelToView can be safely called.
660                     Runnable callScrollToReference = new Runnable() {
661                         public void run() {
662                             URL u = (URL) getDocument().getProperty
663                                 (Document.StreamDescriptionProperty);
664                             String ref = u.getRef();
665                             scrollToReference(ref);
666                         }
667                     };
668                     SwingUtilities.invokeLater(callScrollToReference);
669                 }
670                 pageLoaded = true;
671             } catch (IOException ioe) {
672                 UIManager.getLookAndFeel().provideErrorFeedback(JEditorPane.this);
673             } finally {
674                 if (pageLoaded) {
675                     SwingUtilities.invokeLater(new Runnable() {
676                         public void run() {
677                             JEditorPane.this.firePropertyChange("page", old, page);
678                         }
679                     });
680                 }
681                 return (pageLoaded ? page : old);
682             }
683         }
684 
685         /**
686          * The stream to load the document with
687          */
688         InputStream in;
689 
690         /**
691          * URL of the old page that was replaced (for the property change event)
692          */
693         URL old;
694 
695         /**
696          * URL of the page being loaded (for the property change event)
697          */
698         URL page;
699 
700         /**
701          * The Document instance to load into. This is cached in case a
702          * new Document is created between the time the thread this is created
703          * and run.
704          */
705         Document doc;
706     }
707 
708     /**
709      * Fetches a stream for the given URL, which is about to
710      * be loaded by the <code>setPage</code> method.  By
711      * default, this simply opens the URL and returns the
712      * stream.  This can be reimplemented to do useful things
713      * like fetch the stream from a cache, monitor the progress
714      * of the stream, etc.
715      * <p>
716      * This method is expected to have the the side effect of
717      * establishing the content type, and therefore setting the
718      * appropriate <code>EditorKit</code> to use for loading the stream.
719      * <p>
720      * If this the stream was an http connection, redirects
721      * will be followed and the resulting URL will be set as
722      * the <code>Document.StreamDescriptionProperty</code> so that relative
723      * URL's can be properly resolved.
724      *
725      * @param page  the URL of the page
726      */
727     protected InputStream getStream(URL page) throws IOException {
728         final URLConnection conn = page.openConnection();
729         if (conn instanceof HttpURLConnection) {
730             HttpURLConnection hconn = (HttpURLConnection) conn;
731             hconn.setInstanceFollowRedirects(false);
732             Object postData = getPostData();
733             if (postData != null) {
734                 handlePostData(hconn, postData);
735             }
736             int response = hconn.getResponseCode();
737             boolean redirect = (response >= 300 && response <= 399);
738 
739             /*
740              * In the case of a redirect, we want to actually change the URL
741              * that was input to the new, redirected URL
742              */
743             if (redirect) {
744                 String loc = conn.getHeaderField("Location");
745                 if (loc.startsWith("http", 0)) {
746                     page = new URL(loc);
747                 } else {
748                     page = new URL(page, loc);
749                 }
750                 return getStream(page);
751             }
752         }
753 
754         // Connection properties handler should be forced to run on EDT,
755         // as it instantiates the EditorKit.
756         if (SwingUtilities.isEventDispatchThread()) {
757             handleConnectionProperties(conn);
758         } else {
759             try {
760                 SwingUtilities.invokeAndWait(new Runnable() {
761                     public void run() {
762                         handleConnectionProperties(conn);
763                     }
764                 });
765             } catch (InterruptedException e) {
766                 throw new RuntimeException(e);
767             } catch (InvocationTargetException e) {
768                 throw new RuntimeException(e);
769             }
770         }
771         return conn.getInputStream();
772     }
773 
774     /**
775      * Handle URL connection properties (most notably, content type).
776      */
777     private void handleConnectionProperties(URLConnection conn) {
778         if (pageProperties == null) {
779             pageProperties = new Hashtable<String, Object>();
780         }
781         String type = conn.getContentType();
782         if (type != null) {
783             setContentType(type);
784             pageProperties.put("content-type", type);
785         }
786         pageProperties.put(Document.StreamDescriptionProperty, conn.getURL());
787         String enc = conn.getContentEncoding();
788         if (enc != null) {
789             pageProperties.put("content-encoding", enc);
790         }
791     }
792 
793     private Object getPostData() {
794         return getDocument().getProperty(PostDataProperty);
795     }
796 
797     private void handlePostData(HttpURLConnection conn, Object postData)
798                                                             throws IOException {
799         conn.setDoOutput(true);
800         DataOutputStream os = null;
801         try {
802             conn.setRequestProperty("Content-Type",
803                     "application/x-www-form-urlencoded");
804             os = new DataOutputStream(conn.getOutputStream());
805             os.writeBytes((String) postData);
806         } finally {
807             if (os != null) {
808                 os.close();
809             }
810         }
811     }
812 
813 
814     /**
815      * Scrolls the view to the given reference location
816      * (that is, the value returned by the <code>UL.getRef</code>
817      * method for the URL being displayed).  By default, this
818      * method only knows how to locate a reference in an
819      * HTMLDocument.  The implementation calls the
820      * <code>scrollRectToVisible</code> method to
821      * accomplish the actual scrolling.  If scrolling to a
822      * reference location is needed for document types other
823      * than HTML, this method should be reimplemented.
824      * This method will have no effect if the component
825      * is not visible.
826      *
827      * @param reference the named location to scroll to
828      */
829     public void scrollToReference(String reference) {
830         Document d = getDocument();
831         if (d instanceof HTMLDocument) {
832             HTMLDocument doc = (HTMLDocument) d;
833             HTMLDocument.Iterator iter = doc.getIterator(HTML.Tag.A);
834             for (; iter.isValid(); iter.next()) {
835                 AttributeSet a = iter.getAttributes();
836                 String nm = (String) a.getAttribute(HTML.Attribute.NAME);
837                 if ((nm != null) && nm.equals(reference)) {
838                     // found a matching reference in the document.
839                     try {
840                         int pos = iter.getStartOffset();
841                         Rectangle r = modelToView(pos);
842                         if (r != null) {
843                             // the view is visible, scroll it to the
844                             // center of the current visible area.
845                             Rectangle vis = getVisibleRect();
846                             //r.y -= (vis.height / 2);
847                             r.height = vis.height;
848                             scrollRectToVisible(r);
849                             setCaretPosition(pos);
850                         }
851                     } catch (BadLocationException ble) {
852                         UIManager.getLookAndFeel().provideErrorFeedback(JEditorPane.this);
853                     }
854                 }
855             }
856         }
857     }
858 
859     /**
860      * Gets the current URL being displayed.  If a URL was
861      * not specified in the creation of the document, this
862      * will return <code>null</code>, and relative URL's will not be
863      * resolved.
864      *
865      * @return the URL, or <code>null</code> if none
866      */
867     public URL getPage() {
868         return (URL) getDocument().getProperty(Document.StreamDescriptionProperty);
869     }
870 
871     /**
872      * Sets the current URL being displayed.
873      *
874      * @param url the URL for display
875      * @exception IOException for a <code>null</code> or invalid URL
876      *          specification
877      */
878     public void setPage(String url) throws IOException {
879         if (url == null) {
880             throw new IOException("invalid url");
881         }
882         URL page = new URL(url);
883         setPage(page);
884     }
885 
886     /**
887      * Gets the class ID for the UI.
888      *
889      * @return the string "EditorPaneUI"
890      * @see JComponent#getUIClassID
891      * @see UIDefaults#getUI
892      */
893     public String getUIClassID() {
894         return uiClassID;
895     }
896 
897     /**
898      * Creates the default editor kit (<code>PlainEditorKit</code>) for when
899      * the component is first created.
900      *
901      * @return the editor kit
902      */
903     protected EditorKit createDefaultEditorKit() {
904         return new PlainEditorKit();
905     }
906 
907     /**
908      * Fetches the currently installed kit for handling content.
909      * <code>createDefaultEditorKit</code> is called to set up a default
910      * if necessary.
911      *
912      * @return the editor kit
913      */
914     public EditorKit getEditorKit() {
915         if (kit == null) {
916             kit = createDefaultEditorKit();
917             isUserSetEditorKit = false;
918         }
919         return kit;
920     }
921 
922     /**
923      * Gets the type of content that this editor
924      * is currently set to deal with.  This is
925      * defined to be the type associated with the
926      * currently installed <code>EditorKit</code>.
927      *
928      * @return the content type, <code>null</code> if no editor kit set
929      */
930     public final String getContentType() {
931         return (kit != null) ? kit.getContentType() : null;
932     }
933 
934     /**
935      * Sets the type of content that this editor
936      * handles.  This calls <code>getEditorKitForContentType</code>,
937      * and then <code>setEditorKit</code> if an editor kit can
938      * be successfully located.  This is mostly convenience method
939      * that can be used as an alternative to calling
940      * <code>setEditorKit</code> directly.
941      * <p>
942      * If there is a charset definition specified as a parameter
943      * of the content type specification, it will be used when
944      * loading input streams using the associated <code>EditorKit</code>.
945      * For example if the type is specified as
946      * <code>text/html; charset=EUC-JP</code> the content
947      * will be loaded using the <code>EditorKit</code> registered for
948      * <code>text/html</code> and the Reader provided to
949      * the <code>EditorKit</code> to load unicode into the document will
950      * use the <code>EUC-JP</code> charset for translating
951      * to unicode.  If the type is not recognized, the content
952      * will be loaded using the <code>EditorKit</code> registered
953      * for plain text, <code>text/plain</code>.
954      *
955      * @param type the non-<code>null</code> mime type for the content editing
956      *   support
957      * @see #getContentType
958      * @beaninfo
959      *  description: the type of content
960      * @throws NullPointerException if the <code>type</code> parameter
961      *          is <code>null</code>
962      */
963     public final void setContentType(String type) {
964         // The type could have optional info is part of it,
965         // for example some charset info.  We need to strip that
966         // of and save it.
967         int parm = type.indexOf(";");
968         if (parm > -1) {
969             // Save the paramList.
970             String paramList = type.substring(parm);
971             // update the content type string.
972             type = type.substring(0, parm).trim();
973             if (type.toLowerCase().startsWith("text/")) {
974                 setCharsetFromContentTypeParameters(paramList);
975             }
976         }
977         if ((kit == null) || (! type.equals(kit.getContentType()))
978                 || !isUserSetEditorKit) {
979             EditorKit k = getEditorKitForContentType(type);
980             if (k != null && k != kit) {
981                 setEditorKit(k);
982                 isUserSetEditorKit = false;
983             }
984         }
985 
986     }
987 
988     /**
989      * This method gets the charset information specified as part
990      * of the content type in the http header information.
991      */
992     private void setCharsetFromContentTypeParameters(String paramlist) {
993         String charset;
994         try {
995             // paramlist is handed to us with a leading ';', strip it.
996             int semi = paramlist.indexOf(';');
997             if (semi > -1 && semi < paramlist.length()-1) {
998                 paramlist = paramlist.substring(semi + 1);
999             }
1000 
1001             if (paramlist.length() > 0) {
1002                 // parse the paramlist into attr-value pairs & get the
1003                 // charset pair's value
1004                 HeaderParser hdrParser = new HeaderParser(paramlist);
1005                 charset = hdrParser.findValue("charset");
1006                 if (charset != null) {
1007                     putClientProperty("charset", charset);
1008                 }
1009             }
1010         }
1011         catch (IndexOutOfBoundsException e) {
1012             // malformed parameter list, use charset we have
1013         }
1014         catch (NullPointerException e) {
1015             // malformed parameter list, use charset we have
1016         }
1017         catch (Exception e) {
1018             // malformed parameter list, use charset we have; but complain
1019             System.err.println("JEditorPane.getCharsetFromContentTypeParameters failed on: " + paramlist);
1020             e.printStackTrace();
1021         }
1022     }
1023 
1024 
1025     /**
1026      * Sets the currently installed kit for handling
1027      * content.  This is the bound property that
1028      * establishes the content type of the editor.
1029      * Any old kit is first deinstalled, then if kit is
1030      * non-<code>null</code>,
1031      * the new kit is installed, and a default document created for it.
1032      * A <code>PropertyChange</code> event ("editorKit") is always fired when
1033      * <code>setEditorKit</code> is called.
1034      * <p>
1035      * <em>NOTE: This has the side effect of changing the model,
1036      * because the <code>EditorKit</code> is the source of how a
1037      * particular type
1038      * of content is modeled.  This method will cause <code>setDocument</code>
1039      * to be called on behalf of the caller to ensure integrity
1040      * of the internal state.</em>
1041      *
1042      * @param kit the desired editor behavior
1043      * @see #getEditorKit
1044      * @beaninfo
1045      *  description: the currently installed kit for handling content
1046      *        bound: true
1047      *       expert: true
1048      */
1049     public void setEditorKit(EditorKit kit) {
1050         EditorKit old = this.kit;
1051         isUserSetEditorKit = true;
1052         if (old != null) {
1053             old.deinstall(this);
1054         }
1055         this.kit = kit;
1056         if (this.kit != null) {
1057             this.kit.install(this);
1058             setDocument(this.kit.createDefaultDocument());
1059         }
1060         firePropertyChange("editorKit", old, kit);
1061     }
1062 
1063     /**
1064      * Fetches the editor kit to use for the given type
1065      * of content.  This is called when a type is requested
1066      * that doesn't match the currently installed type.
1067      * If the component doesn't have an <code>EditorKit</code> registered
1068      * for the given type, it will try to create an
1069      * <code>EditorKit</code> from the default <code>EditorKit</code> registry.
1070      * If that fails, a <code>PlainEditorKit</code> is used on the
1071      * assumption that all text documents can be represented
1072      * as plain text.
1073      * <p>
1074      * This method can be reimplemented to use some
1075      * other kind of type registry.  This can
1076      * be reimplemented to use the Java Activation
1077      * Framework, for example.
1078      *
1079      * @param type the non-<code>null</code> content type
1080      * @return the editor kit
1081      */
1082     public EditorKit getEditorKitForContentType(String type) {
1083         if (typeHandlers == null) {
1084             typeHandlers = new Hashtable<String, EditorKit>(3);
1085         }
1086         EditorKit k = typeHandlers.get(type);
1087         if (k == null) {
1088             k = createEditorKitForContentType(type);
1089             if (k != null) {
1090                 setEditorKitForContentType(type, k);
1091             }
1092         }
1093         if (k == null) {
1094             k = createDefaultEditorKit();
1095         }
1096         return k;
1097     }
1098 
1099     /**
1100      * Directly sets the editor kit to use for the given type.  A
1101      * look-and-feel implementation might use this in conjunction
1102      * with <code>createEditorKitForContentType</code> to install handlers for
1103      * content types with a look-and-feel bias.
1104      *
1105      * @param type the non-<code>null</code> content type
1106      * @param k the editor kit to be set
1107      */
1108     public void setEditorKitForContentType(String type, EditorKit k) {
1109         if (typeHandlers == null) {
1110             typeHandlers = new Hashtable<String, EditorKit>(3);
1111         }
1112         typeHandlers.put(type, k);
1113     }
1114 
1115     /**
1116      * Replaces the currently selected content with new content
1117      * represented by the given string.  If there is no selection
1118      * this amounts to an insert of the given text.  If there
1119      * is no replacement text (i.e. the content string is empty
1120      * or <code>null</code>) this amounts to a removal of the
1121      * current selection.  The replacement text will have the
1122      * attributes currently defined for input.  If the component is not
1123      * editable, beep and return.
1124      *
1125      * @param content  the content to replace the selection with.  This
1126      *   value can be <code>null</code>
1127      */
1128     @Override
1129     public void replaceSelection(String content) {
1130         if (! isEditable()) {
1131             UIManager.getLookAndFeel().provideErrorFeedback(JEditorPane.this);
1132             return;
1133         }
1134         EditorKit kit = getEditorKit();
1135         if(kit instanceof StyledEditorKit) {
1136             try {
1137                 Document doc = getDocument();
1138                 Caret caret = getCaret();
1139                 boolean composedTextSaved = saveComposedText(caret.getDot());
1140                 int p0 = Math.min(caret.getDot(), caret.getMark());
1141                 int p1 = Math.max(caret.getDot(), caret.getMark());
1142                 if (doc instanceof AbstractDocument) {
1143                     ((AbstractDocument)doc).replace(p0, p1 - p0, content,
1144                               ((StyledEditorKit)kit).getInputAttributes());
1145                 }
1146                 else {
1147                     if (p0 != p1) {
1148                         doc.remove(p0, p1 - p0);
1149                     }
1150                     if (content != null && content.length() > 0) {
1151                         doc.insertString(p0, content, ((StyledEditorKit)kit).
1152                                          getInputAttributes());
1153                     }
1154                 }
1155                 if (composedTextSaved) {
1156                     restoreComposedText();
1157                 }
1158             } catch (BadLocationException e) {
1159                 UIManager.getLookAndFeel().provideErrorFeedback(JEditorPane.this);
1160             }
1161         }
1162         else {
1163             super.replaceSelection(content);
1164         }
1165     }
1166 
1167     /**
1168      * Creates a handler for the given type from the default registry
1169      * of editor kits.  The registry is created if necessary.  If the
1170      * registered class has not yet been loaded, an attempt
1171      * is made to dynamically load the prototype of the kit for the
1172      * given type.  If the type was registered with a <code>ClassLoader</code>,
1173      * that <code>ClassLoader</code> will be used to load the prototype.
1174      * If there was no registered <code>ClassLoader</code>,
1175      * <code>Class.forName</code> will be used to load the prototype.
1176      * <p>
1177      * Once a prototype <code>EditorKit</code> instance is successfully
1178      * located, it is cloned and the clone is returned.
1179      *
1180      * @param type the content type
1181      * @return the editor kit, or <code>null</code> if there is nothing
1182      *   registered for the given type
1183      */
1184     public static EditorKit createEditorKitForContentType(String type) {
1185         Hashtable<String, EditorKit> kitRegistry = getKitRegisty();
1186         EditorKit k = kitRegistry.get(type);
1187         if (k == null) {
1188             // try to dynamically load the support
1189             String classname = getKitTypeRegistry().get(type);
1190             ClassLoader loader = getKitLoaderRegistry().get(type);
1191             try {
1192                 Class c;
1193                 if (loader != null) {
1194                     c = loader.loadClass(classname);
1195                 } else {
1196                     // Will only happen if developer has invoked
1197                     // registerEditorKitForContentType(type, class, null).
1198                     c = Class.forName(classname, true, Thread.currentThread().
1199                                       getContextClassLoader());
1200                 }
1201                 k = (EditorKit) c.newInstance();
1202                 kitRegistry.put(type, k);
1203             } catch (Throwable e) {
1204                 k = null;
1205             }
1206         }
1207 
1208         // create a copy of the prototype or null if there
1209         // is no prototype.
1210         if (k != null) {
1211             return (EditorKit) k.clone();
1212         }
1213         return null;
1214     }
1215 
1216     /**
1217      * Establishes the default bindings of <code>type</code> to
1218      * <code>classname</code>.
1219      * The class will be dynamically loaded later when actually
1220      * needed, and can be safely changed before attempted uses
1221      * to avoid loading unwanted classes.  The prototype
1222      * <code>EditorKit</code> will be loaded with <code>Class.forName</code>
1223      * when registered with this method.
1224      *
1225      * @param type the non-<code>null</code> content type
1226      * @param classname the class to load later
1227      */
1228     public static void registerEditorKitForContentType(String type, String classname) {
1229         registerEditorKitForContentType(type, classname,Thread.currentThread().
1230                                         getContextClassLoader());
1231     }
1232 
1233     /**
1234      * Establishes the default bindings of <code>type</code> to
1235      * <code>classname</code>.
1236      * The class will be dynamically loaded later when actually
1237      * needed using the given <code>ClassLoader</code>,
1238      * and can be safely changed
1239      * before attempted uses to avoid loading unwanted classes.
1240      *
1241      * @param type the non-<code>null</code> content type
1242      * @param classname the class to load later
1243      * @param loader the <code>ClassLoader</code> to use to load the name
1244      */
1245     public static void registerEditorKitForContentType(String type, String classname, ClassLoader loader) {
1246         getKitTypeRegistry().put(type, classname);
1247         getKitLoaderRegistry().put(type, loader);
1248         getKitRegisty().remove(type);
1249     }
1250 
1251     /**
1252      * Returns the currently registered <code>EditorKit</code>
1253      * class name for the type <code>type</code>.
1254      *
1255      * @param type  the non-<code>null</code> content type
1256      *
1257      * @since 1.3
1258      */
1259     public static String getEditorKitClassNameForContentType(String type) {
1260         return getKitTypeRegistry().get(type);
1261     }
1262 
1263     private static Hashtable<String, String> getKitTypeRegistry() {
1264         loadDefaultKitsIfNecessary();
1265         return (Hashtable)SwingUtilities.appContextGet(kitTypeRegistryKey);
1266     }
1267 
1268     private static Hashtable<String, ClassLoader> getKitLoaderRegistry() {
1269         loadDefaultKitsIfNecessary();
1270         return (Hashtable)SwingUtilities.appContextGet(kitLoaderRegistryKey);
1271     }
1272 
1273     private static Hashtable<String, EditorKit> getKitRegisty() {
1274         Hashtable ht = (Hashtable)SwingUtilities.appContextGet(kitRegistryKey);
1275         if (ht == null) {
1276             ht = new Hashtable(3);
1277             SwingUtilities.appContextPut(kitRegistryKey, ht);
1278         }
1279         return ht;
1280     }
1281 
1282     /**
1283      * This is invoked every time the registries are accessed. Loading
1284      * is done this way instead of via a static as the static is only
1285      * called once when running in plugin resulting in the entries only
1286      * appearing in the first applet.
1287      */
1288     private static void loadDefaultKitsIfNecessary() {
1289         if (SwingUtilities.appContextGet(kitTypeRegistryKey) == null) {
1290             synchronized(defaultEditorKitMap) {
1291                 if (defaultEditorKitMap.size() == 0) {
1292                     defaultEditorKitMap.put("text/plain",
1293                                             "javax.swing.JEditorPane$PlainEditorKit");
1294                     defaultEditorKitMap.put("text/html",
1295                                             "javax.swing.text.html.HTMLEditorKit");
1296                     defaultEditorKitMap.put("text/rtf",
1297                                             "javax.swing.text.rtf.RTFEditorKit");
1298                     defaultEditorKitMap.put("application/rtf",
1299                                             "javax.swing.text.rtf.RTFEditorKit");
1300                 }
1301             }
1302             Hashtable ht = new Hashtable();
1303             SwingUtilities.appContextPut(kitTypeRegistryKey, ht);
1304             ht = new Hashtable();
1305             SwingUtilities.appContextPut(kitLoaderRegistryKey, ht);
1306             for (String key : defaultEditorKitMap.keySet()) {
1307                 registerEditorKitForContentType(key,defaultEditorKitMap.get(key));
1308             }
1309 
1310         }
1311     }
1312 
1313     // --- java.awt.Component methods --------------------------
1314 
1315     /**
1316      * Returns the preferred size for the <code>JEditorPane</code>.
1317      * The preferred size for <code>JEditorPane</code> is slightly altered
1318      * from the preferred size of the superclass.  If the size
1319      * of the viewport has become smaller than the minimum size
1320      * of the component, the scrollable definition for tracking
1321      * width or height will turn to false.  The default viewport
1322      * layout will give the preferred size, and that is not desired
1323      * in the case where the scrollable is tracking.  In that case
1324      * the <em>normal</em> preferred size is adjusted to the
1325      * minimum size.  This allows things like HTML tables to
1326      * shrink down to their minimum size and then be laid out at
1327      * their minimum size, refusing to shrink any further.
1328      *
1329      * @return a <code>Dimension</code> containing the preferred size
1330      */
1331     public Dimension getPreferredSize() {
1332         Dimension d = super.getPreferredSize();
1333         Container parent = SwingUtilities.getUnwrappedParent(this);
1334         if (parent instanceof JViewport) {
1335             JViewport port = (JViewport) parent;
1336             TextUI ui = getUI();
1337             int prefWidth = d.width;
1338             int prefHeight = d.height;
1339             if (! getScrollableTracksViewportWidth()) {
1340                 int w = port.getWidth();
1341                 Dimension min = ui.getMinimumSize(this);
1342                 if (w != 0 && w < min.width) {
1343                     // Only adjust to min if we have a valid size
1344                     prefWidth = min.width;
1345                 }
1346             }
1347             if (! getScrollableTracksViewportHeight()) {
1348                 int h = port.getHeight();
1349                 Dimension min = ui.getMinimumSize(this);
1350                 if (h != 0 && h < min.height) {
1351                     // Only adjust to min if we have a valid size
1352                     prefHeight = min.height;
1353                 }
1354             }
1355             if (prefWidth != d.width || prefHeight != d.height) {
1356                 d = new Dimension(prefWidth, prefHeight);
1357             }
1358         }
1359         return d;
1360     }
1361 
1362     // --- JTextComponent methods -----------------------------
1363 
1364     /**
1365      * Sets the text of this <code>TextComponent</code> to the specified
1366      * content,
1367      * which is expected to be in the format of the content type of
1368      * this editor.  For example, if the type is set to <code>text/html</code>
1369      * the string should be specified in terms of HTML.
1370      * <p>
1371      * This is implemented to remove the contents of the current document,
1372      * and replace them by parsing the given string using the current
1373      * <code>EditorKit</code>.  This gives the semantics of the
1374      * superclass by not changing
1375      * out the model, while supporting the content type currently set on
1376      * this component.  The assumption is that the previous content is
1377      * relatively
1378      * small, and that the previous content doesn't have side effects.
1379      * Both of those assumptions can be violated and cause undesirable results.
1380      * To avoid this, create a new document,
1381      * <code>getEditorKit().createDefaultDocument()</code>, and replace the
1382      * existing <code>Document</code> with the new one. You are then assured the
1383      * previous <code>Document</code> won't have any lingering state.
1384      * <ol>
1385      * <li>
1386      * Leaving the existing model in place means that the old view will be
1387      * torn down, and a new view created, where replacing the document would
1388      * avoid the tear down of the old view.
1389      * <li>
1390      * Some formats (such as HTML) can install things into the document that
1391      * can influence future contents.  HTML can have style information embedded
1392      * that would influence the next content installed unexpectedly.
1393      * </ol>
1394      * <p>
1395      * An alternative way to load this component with a string would be to
1396      * create a StringReader and call the read method.  In this case the model
1397      * would be replaced after it was initialized with the contents of the
1398      * string.
1399      *
1400      * @param t the new text to be set; if <code>null</code> the old
1401      *    text will be deleted
1402      * @see #getText
1403      * @beaninfo
1404      * description: the text of this component
1405      */
1406     public void setText(String t) {
1407         try {
1408             Document doc = getDocument();
1409             doc.remove(0, doc.getLength());
1410             if (t == null || t.equals("")) {
1411                 return;
1412             }
1413             Reader r = new StringReader(t);
1414             EditorKit kit = getEditorKit();
1415             kit.read(r, doc, 0);
1416         } catch (IOException ioe) {
1417             UIManager.getLookAndFeel().provideErrorFeedback(JEditorPane.this);
1418         } catch (BadLocationException ble) {
1419             UIManager.getLookAndFeel().provideErrorFeedback(JEditorPane.this);
1420         }
1421     }
1422 
1423     /**
1424      * Returns the text contained in this <code>TextComponent</code>
1425      * in terms of the
1426      * content type of this editor.  If an exception is thrown while
1427      * attempting to retrieve the text, <code>null</code> will be returned.
1428      * This is implemented to call <code>JTextComponent.write</code> with
1429      * a <code>StringWriter</code>.
1430      *
1431      * @return the text
1432      * @see #setText
1433      */
1434     public String getText() {
1435         String txt;
1436         try {
1437             StringWriter buf = new StringWriter();
1438             write(buf);
1439             txt = buf.toString();
1440         } catch (IOException ioe) {
1441             txt = null;
1442         }
1443         return txt;
1444     }
1445 
1446     // --- Scrollable  ----------------------------------------
1447 
1448     /**
1449      * Returns true if a viewport should always force the width of this
1450      * <code>Scrollable</code> to match the width of the viewport.
1451      *
1452      * @return true if a viewport should force the Scrollables width to
1453      * match its own, false otherwise
1454      */
1455     public boolean getScrollableTracksViewportWidth() {
1456         Container parent = SwingUtilities.getUnwrappedParent(this);
1457         if (parent instanceof JViewport) {
1458             JViewport port = (JViewport) parent;
1459             TextUI ui = getUI();
1460             int w = port.getWidth();
1461             Dimension min = ui.getMinimumSize(this);
1462             Dimension max = ui.getMaximumSize(this);
1463             if ((w >= min.width) && (w <= max.width)) {
1464                 return true;
1465             }
1466         }
1467         return false;
1468     }
1469 
1470     /**
1471      * Returns true if a viewport should always force the height of this
1472      * <code>Scrollable</code> to match the height of the viewport.
1473      *
1474      * @return true if a viewport should force the
1475      *          <code>Scrollable</code>'s height to match its own,
1476      *          false otherwise
1477      */
1478     public boolean getScrollableTracksViewportHeight() {
1479         Container parent = SwingUtilities.getUnwrappedParent(this);
1480         if (parent instanceof JViewport) {
1481             JViewport port = (JViewport) parent;
1482             TextUI ui = getUI();
1483             int h = port.getHeight();
1484             Dimension min = ui.getMinimumSize(this);
1485             if (h >= min.height) {
1486                 Dimension max = ui.getMaximumSize(this);
1487                 if (h <= max.height) {
1488                     return true;
1489                 }
1490             }
1491         }
1492         return false;
1493     }
1494 
1495     // --- Serialization ------------------------------------
1496 
1497     /**
1498      * See <code>readObject</code> and <code>writeObject</code> in
1499      * <code>JComponent</code> for more
1500      * information about serialization in Swing.
1501      */
1502     private void writeObject(ObjectOutputStream s) throws IOException {
1503         s.defaultWriteObject();
1504         if (getUIClassID().equals(uiClassID)) {
1505             byte count = JComponent.getWriteObjCounter(this);
1506             JComponent.setWriteObjCounter(this, --count);
1507             if (count == 0 && ui != null) {
1508                 ui.installUI(this);
1509             }
1510         }
1511     }
1512 
1513     // --- variables ---------------------------------------
1514 
1515     private SwingWorker<URL, Object> pageLoader;
1516 
1517     /**
1518      * Current content binding of the editor.
1519      */
1520     private EditorKit kit;
1521     private boolean isUserSetEditorKit;
1522 
1523     private Hashtable<String, Object> pageProperties;
1524 
1525     /** Should be kept in sync with javax.swing.text.html.FormView counterpart. */
1526     final static String PostDataProperty = "javax.swing.JEditorPane.postdata";
1527 
1528     /**
1529      * Table of registered type handlers for this editor.
1530      */
1531     private Hashtable<String, EditorKit> typeHandlers;
1532 
1533     /*
1534      * Private AppContext keys for this class's static variables.
1535      */
1536     private static final Object kitRegistryKey =
1537         new StringBuffer("JEditorPane.kitRegistry");
1538     private static final Object kitTypeRegistryKey =
1539         new StringBuffer("JEditorPane.kitTypeRegistry");
1540     private static final Object kitLoaderRegistryKey =
1541         new StringBuffer("JEditorPane.kitLoaderRegistry");
1542 
1543     /**
1544      * @see #getUIClassID
1545      * @see #readObject
1546      */
1547     private static final String uiClassID = "EditorPaneUI";
1548 
1549 
1550     /**
1551      * Key for a client property used to indicate whether
1552      * <a href="http://www.w3.org/TR/CSS21/syndata.html#length-units">
1553      * w3c compliant</a> length units are used for html rendering.
1554      * <p>
1555      * By default this is not enabled; to enable
1556      * it set the client {@link #putClientProperty property} with this name
1557      * to <code>Boolean.TRUE</code>.
1558      *
1559      * @since 1.5
1560      */
1561     public static final String W3C_LENGTH_UNITS = "JEditorPane.w3cLengthUnits";
1562 
1563     /**
1564      * Key for a client property used to indicate whether
1565      * the default font and foreground color from the component are
1566      * used if a font or foreground color is not specified in the styled
1567      * text.
1568      * <p>
1569      * The default varies based on the look and feel;
1570      * to enable it set the client {@link #putClientProperty property} with
1571      * this name to <code>Boolean.TRUE</code>.
1572      *
1573      * @since 1.5
1574      */
1575     public static final String HONOR_DISPLAY_PROPERTIES = "JEditorPane.honorDisplayProperties";
1576 
1577     static final Map<String, String> defaultEditorKitMap = new HashMap<String, String>(0);
1578 
1579     /**
1580      * Returns a string representation of this <code>JEditorPane</code>.
1581      * This method
1582      * is intended to be used only for debugging purposes, and the
1583      * content and format of the returned string may vary between
1584      * implementations. The returned string may be empty but may not
1585      * be <code>null</code>.
1586      *
1587      * @return  a string representation of this <code>JEditorPane</code>
1588      */
1589     protected String paramString() {
1590         String kitString = (kit != null ?
1591                             kit.toString() : "");
1592         String typeHandlersString = (typeHandlers != null ?
1593                                      typeHandlers.toString() : "");
1594 
1595         return super.paramString() +
1596         ",kit=" + kitString +
1597         ",typeHandlers=" + typeHandlersString;
1598     }
1599 
1600 
1601 /////////////////
1602 // Accessibility support
1603 ////////////////
1604 
1605 
1606     /**
1607      * Gets the AccessibleContext associated with this JEditorPane.
1608      * For editor panes, the AccessibleContext takes the form of an
1609      * AccessibleJEditorPane.
1610      * A new AccessibleJEditorPane instance is created if necessary.
1611      *
1612      * @return an AccessibleJEditorPane that serves as the
1613      *         AccessibleContext of this JEditorPane
1614      */
1615     public AccessibleContext getAccessibleContext() {
1616         if (getEditorKit() instanceof HTMLEditorKit) {
1617             if (accessibleContext == null || accessibleContext.getClass() !=
1618                     AccessibleJEditorPaneHTML.class) {
1619                 accessibleContext = new AccessibleJEditorPaneHTML();
1620             }
1621         } else if (accessibleContext == null || accessibleContext.getClass() !=
1622                        AccessibleJEditorPane.class) {
1623             accessibleContext = new AccessibleJEditorPane();
1624         }
1625         return accessibleContext;
1626     }
1627 
1628     /**
1629      * This class implements accessibility support for the
1630      * <code>JEditorPane</code> class.  It provides an implementation of the
1631      * Java Accessibility API appropriate to editor pane user-interface
1632      * elements.
1633      * <p>
1634      * <strong>Warning:</strong>
1635      * Serialized objects of this class will not be compatible with
1636      * future Swing releases. The current serialization support is
1637      * appropriate for short term storage or RMI between applications running
1638      * the same version of Swing.  As of 1.4, support for long term storage
1639      * of all JavaBeans<sup><font size="-2">TM</font></sup>
1640      * has been added to the <code>java.beans</code> package.
1641      * Please see {@link java.beans.XMLEncoder}.
1642      */
1643     protected class AccessibleJEditorPane extends AccessibleJTextComponent {
1644 
1645         /**
1646          * Gets the accessibleDescription property of this object.  If this
1647          * property isn't set, returns the content type of this
1648          * <code>JEditorPane</code> instead (e.g. "plain/text", "html/text").
1649          *
1650          * @return the localized description of the object; <code>null</code>
1651          *      if this object does not have a description
1652          *
1653          * @see #setAccessibleName
1654          */
1655         public String getAccessibleDescription() {
1656             String description = accessibleDescription;
1657 
1658             // fallback to client property
1659             if (description == null) {
1660                 description = (String)getClientProperty(AccessibleContext.ACCESSIBLE_DESCRIPTION_PROPERTY);
1661             }
1662             if (description == null) {
1663                 description = JEditorPane.this.getContentType();
1664             }
1665             return description;
1666         }
1667 
1668         /**
1669          * Gets the state set of this object.
1670          *
1671          * @return an instance of AccessibleStateSet describing the states
1672          * of the object
1673          * @see AccessibleStateSet
1674          */
1675         public AccessibleStateSet getAccessibleStateSet() {
1676             AccessibleStateSet states = super.getAccessibleStateSet();
1677             states.add(AccessibleState.MULTI_LINE);
1678             return states;
1679         }
1680     }
1681 
1682     /**
1683      * This class provides support for <code>AccessibleHypertext</code>,
1684      * and is used in instances where the <code>EditorKit</code>
1685      * installed in this <code>JEditorPane</code> is an instance of
1686      * <code>HTMLEditorKit</code>.
1687      * <p>
1688      * <strong>Warning:</strong>
1689      * Serialized objects of this class will not be compatible with
1690      * future Swing releases. The current serialization support is
1691      * appropriate for short term storage or RMI between applications running
1692      * the same version of Swing.  As of 1.4, support for long term storage
1693      * of all JavaBeans<sup><font size="-2">TM</font></sup>
1694      * has been added to the <code>java.beans</code> package.
1695      * Please see {@link java.beans.XMLEncoder}.
1696      */
1697     protected class AccessibleJEditorPaneHTML extends AccessibleJEditorPane {
1698 
1699         private AccessibleContext accessibleContext;
1700 
1701         public AccessibleText getAccessibleText() {
1702             return new JEditorPaneAccessibleHypertextSupport();
1703         }
1704 
1705         protected AccessibleJEditorPaneHTML () {
1706             HTMLEditorKit kit = (HTMLEditorKit)JEditorPane.this.getEditorKit();
1707             accessibleContext = kit.getAccessibleContext();
1708         }
1709 
1710         /**
1711          * Returns the number of accessible children of the object.
1712          *
1713          * @return the number of accessible children of the object.
1714          */
1715         public int getAccessibleChildrenCount() {
1716             if (accessibleContext != null) {
1717                 return accessibleContext.getAccessibleChildrenCount();
1718             } else {
1719                 return 0;
1720             }
1721         }
1722 
1723         /**
1724          * Returns the specified Accessible child of the object.  The Accessible
1725          * children of an Accessible object are zero-based, so the first child
1726          * of an Accessible child is at index 0, the second child is at index 1,
1727          * and so on.
1728          *
1729          * @param i zero-based index of child
1730          * @return the Accessible child of the object
1731          * @see #getAccessibleChildrenCount
1732          */
1733         public Accessible getAccessibleChild(int i) {
1734             if (accessibleContext != null) {
1735                 return accessibleContext.getAccessibleChild(i);
1736             } else {
1737                 return null;
1738             }
1739         }
1740 
1741         /**
1742          * Returns the Accessible child, if one exists, contained at the local
1743          * coordinate Point.
1744          *
1745          * @param p The point relative to the coordinate system of this object.
1746          * @return the Accessible, if it exists, at the specified location;
1747          * otherwise null
1748          */
1749         public Accessible getAccessibleAt(Point p) {
1750             if (accessibleContext != null && p != null) {
1751                 try {
1752                     AccessibleComponent acomp =
1753                         accessibleContext.getAccessibleComponent();
1754                     if (acomp != null) {
1755                         return acomp.getAccessibleAt(p);
1756                     } else {
1757                         return null;
1758                     }
1759                 } catch (IllegalComponentStateException e) {
1760                     return null;
1761                 }
1762             } else {
1763                 return null;
1764             }
1765         }
1766     }
1767 
1768     /**
1769      * What's returned by
1770      * <code>AccessibleJEditorPaneHTML.getAccessibleText</code>.
1771      *
1772      * Provides support for <code>AccessibleHypertext</code> in case
1773      * there is an HTML document being displayed in this
1774      * <code>JEditorPane</code>.
1775      *
1776      */
1777     protected class JEditorPaneAccessibleHypertextSupport
1778     extends AccessibleJEditorPane implements AccessibleHypertext {
1779 
1780         public class HTMLLink extends AccessibleHyperlink {
1781             Element element;
1782 
1783             public HTMLLink(Element e) {
1784                 element = e;
1785             }
1786 
1787             /**
1788              * Since the document a link is associated with may have
1789              * changed, this method returns whether this Link is valid
1790              * anymore (with respect to the document it references).
1791              *
1792              * @return a flag indicating whether this link is still valid with
1793              *         respect to the AccessibleHypertext it belongs to
1794              */
1795             public boolean isValid() {
1796                 return JEditorPaneAccessibleHypertextSupport.this.linksValid;
1797             }
1798 
1799             /**
1800              * Returns the number of accessible actions available in this Link
1801              * If there are more than one, the first one is NOT considered the
1802              * "default" action of this LINK object (e.g. in an HTML imagemap).
1803              * In general, links will have only one AccessibleAction in them.
1804              *
1805              * @return the zero-based number of Actions in this object
1806              */
1807             public int getAccessibleActionCount() {
1808                 return 1;
1809             }
1810 
1811             /**
1812              * Perform the specified Action on the object
1813              *
1814              * @param i zero-based index of actions
1815              * @return true if the the action was performed; else false.
1816              * @see #getAccessibleActionCount
1817              */
1818             public boolean doAccessibleAction(int i) {
1819                 if (i == 0 && isValid() == true) {
1820                     URL u = (URL) getAccessibleActionObject(i);
1821                     if (u != null) {
1822                         HyperlinkEvent linkEvent =
1823                             new HyperlinkEvent(JEditorPane.this, HyperlinkEvent.EventType.ACTIVATED, u);
1824                         JEditorPane.this.fireHyperlinkUpdate(linkEvent);
1825                         return true;
1826                     }
1827                 }
1828                 return false;  // link invalid or i != 0
1829             }
1830 
1831             /**
1832              * Return a String description of this particular
1833              * link action.  The string returned is the text
1834              * within the document associated with the element
1835              * which contains this link.
1836              *
1837              * @param i zero-based index of the actions
1838              * @return a String description of the action
1839              * @see #getAccessibleActionCount
1840              */
1841             public String getAccessibleActionDescription(int i) {
1842                 if (i == 0 && isValid() == true) {
1843                     Document d = JEditorPane.this.getDocument();
1844                     if (d != null) {
1845                         try {
1846                             return d.getText(getStartIndex(),
1847                                              getEndIndex() - getStartIndex());
1848                         } catch (BadLocationException exception) {
1849                             return null;
1850                         }
1851                     }
1852                 }
1853                 return null;
1854             }
1855 
1856             /**
1857              * Returns a URL object that represents the link.
1858              *
1859              * @param i zero-based index of the actions
1860              * @return an URL representing the HTML link itself
1861              * @see #getAccessibleActionCount
1862              */
1863             public Object getAccessibleActionObject(int i) {
1864                 if (i == 0 && isValid() == true) {
1865                     AttributeSet as = element.getAttributes();
1866                     AttributeSet anchor =
1867                         (AttributeSet) as.getAttribute(HTML.Tag.A);
1868                     String href = (anchor != null) ?
1869                         (String) anchor.getAttribute(HTML.Attribute.HREF) : null;
1870                     if (href != null) {
1871                         URL u;
1872                         try {
1873                             u = new URL(JEditorPane.this.getPage(), href);
1874                         } catch (MalformedURLException m) {
1875                             u = null;
1876                         }
1877                         return u;
1878                     }
1879                 }
1880                 return null;  // link invalid or i != 0
1881             }
1882 
1883             /**
1884              * Return an object that represents the link anchor,
1885              * as appropriate for that link.  E.g. from HTML:
1886              *   <a href="http://www.sun.com/access">Accessibility</a>
1887              * this method would return a String containing the text:
1888              * 'Accessibility'.
1889              *
1890              * Similarly, from this HTML:
1891              *   &lt;a HREF="#top"&gt;&lt;img src="top-hat.gif" alt="top hat"&gt;&lt;/a&gt;
1892              * this might return the object ImageIcon("top-hat.gif", "top hat");
1893              *
1894              * @param i zero-based index of the actions
1895              * @return an Object representing the hypertext anchor
1896              * @see #getAccessibleActionCount
1897              */
1898             public Object getAccessibleActionAnchor(int i) {
1899                 return getAccessibleActionDescription(i);
1900             }
1901 
1902 
1903             /**
1904              * Get the index with the hypertext document at which this
1905              * link begins
1906              *
1907              * @return index of start of link
1908              */
1909             public int getStartIndex() {
1910                 return element.getStartOffset();
1911             }
1912 
1913             /**
1914              * Get the index with the hypertext document at which this
1915              * link ends
1916              *
1917              * @return index of end of link
1918              */
1919             public int getEndIndex() {
1920                 return element.getEndOffset();
1921             }
1922         }
1923 
1924         private class LinkVector extends Vector<HTMLLink> {
1925             public int baseElementIndex(Element e) {
1926                 HTMLLink l;
1927                 for (int i = 0; i < elementCount; i++) {
1928                     l = elementAt(i);
1929                     if (l.element == e) {
1930                         return i;
1931                     }
1932                 }
1933                 return -1;
1934             }
1935         }
1936 
1937         LinkVector hyperlinks;
1938         boolean linksValid = false;
1939 
1940         /**
1941          * Build the private table mapping links to locations in the text
1942          */
1943         private void buildLinkTable() {
1944             hyperlinks.removeAllElements();
1945             Document d = JEditorPane.this.getDocument();
1946             if (d != null) {
1947                 ElementIterator ei = new ElementIterator(d);
1948                 Element e;
1949                 AttributeSet as;
1950                 AttributeSet anchor;
1951                 String href;
1952                 while ((e = ei.next()) != null) {
1953                     if (e.isLeaf()) {
1954                         as = e.getAttributes();
1955                     anchor = (AttributeSet) as.getAttribute(HTML.Tag.A);
1956                     href = (anchor != null) ?
1957                         (String) anchor.getAttribute(HTML.Attribute.HREF) : null;
1958                         if (href != null) {
1959                             hyperlinks.addElement(new HTMLLink(e));
1960                         }
1961                     }
1962                 }
1963             }
1964             linksValid = true;
1965         }
1966 
1967         /**
1968          * Make one of these puppies
1969          */
1970         public JEditorPaneAccessibleHypertextSupport() {
1971             hyperlinks = new LinkVector();
1972             Document d = JEditorPane.this.getDocument();
1973             if (d != null) {
1974                 d.addDocumentListener(new DocumentListener() {
1975                     public void changedUpdate(DocumentEvent theEvent) {
1976                         linksValid = false;
1977                     }
1978                     public void insertUpdate(DocumentEvent theEvent) {
1979                         linksValid = false;
1980                     }
1981                     public void removeUpdate(DocumentEvent theEvent) {
1982                         linksValid = false;
1983                     }
1984                 });
1985             }
1986         }
1987 
1988         /**
1989          * Returns the number of links within this hypertext doc.
1990          *
1991          * @return number of links in this hypertext doc.
1992          */
1993         public int getLinkCount() {
1994             if (linksValid == false) {
1995                 buildLinkTable();
1996             }
1997             return hyperlinks.size();
1998         }
1999 
2000         /**
2001          * Returns the index into an array of hyperlinks that
2002          * is associated with this character index, or -1 if there
2003          * is no hyperlink associated with this index.
2004          *
2005          * @param  charIndex index within the text
2006          * @return index into the set of hyperlinks for this hypertext doc.
2007          */
2008         public int getLinkIndex(int charIndex) {
2009             if (linksValid == false) {
2010                 buildLinkTable();
2011             }
2012             Element e = null;
2013             Document doc = JEditorPane.this.getDocument();
2014             if (doc != null) {
2015                 for (e = doc.getDefaultRootElement(); ! e.isLeaf(); ) {
2016                     int index = e.getElementIndex(charIndex);
2017                     e = e.getElement(index);
2018                 }
2019             }
2020 
2021             // don't need to verify that it's an HREF element; if
2022             // not, then it won't be in the hyperlinks Vector, and
2023             // so indexOf will return -1 in any case
2024             return hyperlinks.baseElementIndex(e);
2025         }
2026 
2027         /**
2028          * Returns the index into an array of hyperlinks that
2029          * index.  If there is no hyperlink at this index, it returns
2030          * null.
2031          *
2032          * @param linkIndex into the set of hyperlinks for this hypertext doc.
2033          * @return string representation of the hyperlink
2034          */
2035         public AccessibleHyperlink getLink(int linkIndex) {
2036             if (linksValid == false) {
2037                 buildLinkTable();
2038             }
2039             if (linkIndex >= 0 && linkIndex < hyperlinks.size()) {
2040                 return hyperlinks.elementAt(linkIndex);
2041             } else {
2042                 return null;
2043             }
2044         }
2045 
2046         /**
2047          * Returns the contiguous text within the document that
2048          * is associated with this hyperlink.
2049          *
2050          * @param linkIndex into the set of hyperlinks for this hypertext doc.
2051          * @return the contiguous text sharing the link at this index
2052          */
2053         public String getLinkText(int linkIndex) {
2054             if (linksValid == false) {
2055                 buildLinkTable();
2056             }
2057             Element e = (Element) hyperlinks.elementAt(linkIndex);
2058             if (e != null) {
2059                 Document d = JEditorPane.this.getDocument();
2060                 if (d != null) {
2061                     try {
2062                         return d.getText(e.getStartOffset(),
2063                                          e.getEndOffset() - e.getStartOffset());
2064                     } catch (BadLocationException exception) {
2065                         return null;
2066                     }
2067                 }
2068             }
2069             return null;
2070         }
2071     }
2072 
2073     static class PlainEditorKit extends DefaultEditorKit implements ViewFactory {
2074 
2075         /**
2076          * Fetches a factory that is suitable for producing
2077          * views of any models that are produced by this
2078          * kit.  The default is to have the UI produce the
2079          * factory, so this method has no implementation.
2080          *
2081          * @return the view factory
2082          */
2083         public ViewFactory getViewFactory() {
2084             return this;
2085         }
2086 
2087         /**
2088          * Creates a view from the given structural element of a
2089          * document.
2090          *
2091          * @param elem  the piece of the document to build a view of
2092          * @return the view
2093          * @see View
2094          */
2095         public View create(Element elem) {
2096             Document doc = elem.getDocument();
2097             Object i18nFlag
2098                 = doc.getProperty("i18n"/*AbstractDocument.I18NProperty*/);
2099             if ((i18nFlag != null) && i18nFlag.equals(Boolean.TRUE)) {
2100                 // build a view that support bidi
2101                 return createI18N(elem);
2102             } else {
2103                 return new WrappedPlainView(elem);
2104             }
2105         }
2106 
2107         View createI18N(Element elem) {
2108             String kind = elem.getName();
2109             if (kind != null) {
2110                 if (kind.equals(AbstractDocument.ContentElementName)) {
2111                     return new PlainParagraph(elem);
2112                 } else if (kind.equals(AbstractDocument.ParagraphElementName)){
2113                     return new BoxView(elem, View.Y_AXIS);
2114                 }
2115             }
2116             return null;
2117         }
2118 
2119         /**
2120          * Paragraph for representing plain-text lines that support
2121          * bidirectional text.
2122          */
2123         static class PlainParagraph extends javax.swing.text.ParagraphView {
2124 
2125             PlainParagraph(Element elem) {
2126                 super(elem);
2127                 layoutPool = new LogicalView(elem);
2128                 layoutPool.setParent(this);
2129             }
2130 
2131             protected void setPropertiesFromAttributes() {
2132                 Component c = getContainer();
2133                 if ((c != null)
2134                     && (! c.getComponentOrientation().isLeftToRight()))
2135                 {
2136                     setJustification(StyleConstants.ALIGN_RIGHT);
2137                 } else {
2138                     setJustification(StyleConstants.ALIGN_LEFT);
2139                 }
2140             }
2141 
2142             /**
2143              * Fetch the constraining span to flow against for
2144              * the given child index.
2145              */
2146             public int getFlowSpan(int index) {
2147                 Component c = getContainer();
2148                 if (c instanceof JTextArea) {
2149                     JTextArea area = (JTextArea) c;
2150                     if (! area.getLineWrap()) {
2151                         // no limit if unwrapped
2152                         return Integer.MAX_VALUE;
2153                     }
2154                 }
2155                 return super.getFlowSpan(index);
2156             }
2157 
2158             protected SizeRequirements calculateMinorAxisRequirements(int axis,
2159                                                             SizeRequirements r)
2160             {
2161                 SizeRequirements req
2162                     = super.calculateMinorAxisRequirements(axis, r);
2163                 Component c = getContainer();
2164                 if (c instanceof JTextArea) {
2165                     JTextArea area = (JTextArea) c;
2166                     if (! area.getLineWrap()) {
2167                         // min is pref if unwrapped
2168                         req.minimum = req.preferred;
2169                     }
2170                 }
2171                 return req;
2172             }
2173 
2174             /**
2175              * This class can be used to represent a logical view for
2176              * a flow.  It keeps the children updated to reflect the state
2177              * of the model, gives the logical child views access to the
2178              * view hierarchy, and calculates a preferred span.  It doesn't
2179              * do any rendering, layout, or model/view translation.
2180              */
2181             static class LogicalView extends CompositeView {
2182 
2183                 LogicalView(Element elem) {
2184                     super(elem);
2185                 }
2186 
2187                 protected int getViewIndexAtPosition(int pos) {
2188                     Element elem = getElement();
2189                     if (elem.getElementCount() > 0) {
2190                         return elem.getElementIndex(pos);
2191                     }
2192                     return 0;
2193                 }
2194 
2195                 protected boolean
2196                 updateChildren(DocumentEvent.ElementChange ec,
2197                                DocumentEvent e, ViewFactory f)
2198                 {
2199                     return false;
2200                 }
2201 
2202                 protected void loadChildren(ViewFactory f) {
2203                     Element elem = getElement();
2204                     if (elem.getElementCount() > 0) {
2205                         super.loadChildren(f);
2206                     } else {
2207                         View v = new GlyphView(elem);
2208                         append(v);
2209                     }
2210                 }
2211 
2212                 public float getPreferredSpan(int axis) {
2213                     if( getViewCount() != 1 )
2214                         throw new Error("One child view is assumed.");
2215 
2216                     View v = getView(0);
2217                     //((GlyphView)v).setGlyphPainter(null);
2218                     return v.getPreferredSpan(axis);
2219                 }
2220 
2221                 /**
2222                  * Forward the DocumentEvent to the given child view.  This
2223                  * is implemented to reparent the child to the logical view
2224                  * (the children may have been parented by a row in the flow
2225                  * if they fit without breaking) and then execute the
2226                  * superclass behavior.
2227                  *
2228                  * @param v the child view to forward the event to.
2229                  * @param e the change information from the associated document
2230                  * @param a the current allocation of the view
2231                  * @param f the factory to use to rebuild if the view has
2232                  *          children
2233                  * @see #forwardUpdate
2234                  * @since 1.3
2235                  */
2236                 protected void forwardUpdateToView(View v, DocumentEvent e,
2237                                                    Shape a, ViewFactory f) {
2238                     v.setParent(this);
2239                     super.forwardUpdateToView(v, e, a, f);
2240                 }
2241 
2242                 // The following methods don't do anything useful, they
2243                 // simply keep the class from being abstract.
2244 
2245                 public void paint(Graphics g, Shape allocation) {
2246                 }
2247 
2248                 protected boolean isBefore(int x, int y, Rectangle alloc) {
2249                     return false;
2250                 }
2251 
2252                 protected boolean isAfter(int x, int y, Rectangle alloc) {
2253                     return false;
2254                 }
2255 
2256                 protected View getViewAtPoint(int x, int y, Rectangle alloc) {
2257                     return null;
2258                 }
2259 
2260                 protected void childAllocation(int index, Rectangle a) {
2261                 }
2262             }
2263         }
2264     }
2265 
2266 /* This is useful for the nightmare of parsing multi-part HTTP/RFC822 headers
2267  * sensibly:
2268  * From a String like: 'timeout=15, max=5'
2269  * create an array of Strings:
2270  * { {"timeout", "15"},
2271  *   {"max", "5"}
2272  * }
2273  * From one like: 'Basic Realm="FuzzFace" Foo="Biz Bar Baz"'
2274  * create one like (no quotes in literal):
2275  * { {"basic", null},
2276  *   {"realm", "FuzzFace"}
2277  *   {"foo", "Biz Bar Baz"}
2278  * }
2279  * keys are converted to lower case, vals are left as is....
2280  *
2281  * author Dave Brown
2282  */
2283 
2284 
2285 static class HeaderParser {
2286 
2287     /* table of key/val pairs - maxes out at 10!!!!*/
2288     String raw;
2289     String[][] tab;
2290 
2291     public HeaderParser(String raw) {
2292         this.raw = raw;
2293         tab = new String[10][2];
2294         parse();
2295     }
2296 
2297     private void parse() {
2298 
2299         if (raw != null) {
2300             raw = raw.trim();
2301             char[] ca = raw.toCharArray();
2302             int beg = 0, end = 0, i = 0;
2303             boolean inKey = true;
2304             boolean inQuote = false;
2305             int len = ca.length;
2306             while (end < len) {
2307                 char c = ca[end];
2308                 if (c == '=') { // end of a key
2309                     tab[i][0] = new String(ca, beg, end-beg).toLowerCase();
2310                     inKey = false;
2311                     end++;
2312                     beg = end;
2313                 } else if (c == '\"') {
2314                     if (inQuote) {
2315                         tab[i++][1]= new String(ca, beg, end-beg);
2316                         inQuote=false;
2317                         do {
2318                             end++;
2319                         } while (end < len && (ca[end] == ' ' || ca[end] == ','));
2320                         inKey=true;
2321                         beg=end;
2322                     } else {
2323                         inQuote=true;
2324                         end++;
2325                         beg=end;
2326                     }
2327                 } else if (c == ' ' || c == ',') { // end key/val, of whatever we're in
2328                     if (inQuote) {
2329                         end++;
2330                         continue;
2331                     } else if (inKey) {
2332                         tab[i++][0] = (new String(ca, beg, end-beg)).toLowerCase();
2333                     } else {
2334                         tab[i++][1] = (new String(ca, beg, end-beg));
2335                     }
2336                     while (end < len && (ca[end] == ' ' || ca[end] == ',')) {
2337                         end++;
2338                     }
2339                     inKey = true;
2340                     beg = end;
2341                 } else {
2342                     end++;
2343                 }
2344             }
2345             // get last key/val, if any
2346             if (--end > beg) {
2347                 if (!inKey) {
2348                     if (ca[end] == '\"') {
2349                         tab[i++][1] = (new String(ca, beg, end-beg));
2350                     } else {
2351                         tab[i++][1] = (new String(ca, beg, end-beg+1));
2352                     }
2353                 } else {
2354                     tab[i][0] = (new String(ca, beg, end-beg+1)).toLowerCase();
2355                 }
2356             } else if (end == beg) {
2357                 if (!inKey) {
2358                     if (ca[end] == '\"') {
2359                         tab[i++][1] = String.valueOf(ca[end-1]);
2360                     } else {
2361                         tab[i++][1] = String.valueOf(ca[end]);
2362                     }
2363                 } else {
2364                     tab[i][0] = String.valueOf(ca[end]).toLowerCase();
2365                 }
2366             }
2367         }
2368 
2369     }
2370 
2371     public String findKey(int i) {
2372         if (i < 0 || i > 10)
2373             return null;
2374         return tab[i][0];
2375     }
2376 
2377     public String findValue(int i) {
2378         if (i < 0 || i > 10)
2379             return null;
2380         return tab[i][1];
2381     }
2382 
2383     public String findValue(String key) {
2384         return findValue(key, null);
2385     }
2386 
2387     public String findValue(String k, String Default) {
2388         if (k == null)
2389             return Default;
2390         k = k.toLowerCase();
2391         for (int i = 0; i < 10; ++i) {
2392             if (tab[i][0] == null) {
2393                 return Default;
2394             } else if (k.equals(tab[i][0])) {
2395                 return tab[i][1];
2396             }
2397         }
2398         return Default;
2399     }
2400 
2401     public int findInt(String k, int Default) {
2402         try {
2403             return Integer.parseInt(findValue(k, String.valueOf(Default)));
2404         } catch (Throwable t) {
2405             return Default;
2406         }
2407     }
2408  }
2409 
2410 }